<script lang='ts' setup>
import { ref, watch } from 'vue';
import type Node from 'element-plus/es/components/tree/src/model/node'
import { List, Folder, } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus';
import { getMenuList, deleteMenu, addMenu, type MenuInterface, menuDetail, updateMenu, copyMenu, MenuTreeInterface } from '@/api/menuManage';

const visible = ref(false)
const props = withDefaults(defineProps<{ dialogVisible: boolean, appId: string }>(), {
  dialogVisible: false,
})
const emits = defineEmits<{
  (e: 'update:dialogVisible', val: boolean): void,
  (e: 'selectEditPage', val: MenuInterface): void,
}>()

function cancel() {
  emits('update:dialogVisible', false)
  dataSource.value = []
  copyMenuItem.value = null
}

// 获取elplus icons图标列表
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import RightMenu from "@/components/RightMenu.vue";
import { generateRandomString } from '@/utils';
import { useDragMethods } from './hooks/useDragMethods';
import _ from 'lodash';

let elPlusIcons = [] as any
for (const [key] of Object.entries(ElementPlusIconsVue)) {
  elPlusIcons.push(key)
}

const {
  setSort,
  getParentNode,
  resetParentId,
} = useDragMethods()

const rules = {
  label: [
    { required: true, message: '请输入菜单名称', trigger: 'blur' },
  ],
  code: [
    { required: true, message: '请输入菜单英文名', trigger: 'blur' },
    { pattern: /^[a-zA-Z0-9_-]+$/, message: '名称只能包含-、_、数字和英文字母', trigger: 'blur' },
  ],
  isCustom: [
    { required: true, message: '请选择是否自定义页面', trigger: 'none' },
  ],
  data: [
    { required: true, message: '请填写自定义页面文件', trigger: 'blur' },
    {
      pattern: /^(?!\/)(?!.*\.\.vue$)(?!.*\/{2})[a-zA-Z0-9\/]+(?<!\/)$/,
      message: '请按照示例填写正确的文件名',
      trigger: 'blur'
    }
  ]
}
const dataSource = ref<MenuInterface[]>([])
const isAddOrEdit = ref(1)//编辑还是添加 1为添加，2为编辑
const formData = ref<MenuInterface>({
  id: '',
  label: '',
  code: '',
  data: undefined,
  type: '',
  appId: '',
  delFlag: '',
  extra: {
    webShowFlag: 1
  },
  isCustom: false
})

/**
 * 向树形结构的数据中添加一个新的子节点
 *
 * @param data 要添加子节点的树形结构数据
 * @param type 菜单类型，1为目录，2为菜单 3为自定义菜单
 */
function append(data: MenuInterface | null, type: string = '2') {
  isAddOrEdit.value = 1
  let newChild = {
    // id: Date.now() + '',
    label: '菜单名称',
    appId: props.appId,
    parentId: data?.id,
    // children: [],
    code: '',
    data: {},
    fullPath: '',
    isCustom: false,
    type: '1',
    extra: {
      webShowFlag: 1
    }
  } as unknown as MenuInterface
  newChild.type = type
  if (type == '1') {
    newChild.label = '文件夹名称'
  }
  if (type == '2') {
    newChild.label = '菜单名称'
  }
  //暂存当前操作的对象
  currentMenuItemId.value = data ? data.id : null

  // 打开编辑框
  visibleEditItem.value = true
  formData.value = JSON.parse(JSON.stringify(newChild))

}

/**
 * 处理复制菜单项的逻辑
 *
 * @param menuItem 菜单项对象，包含菜单项的ID等信息
 */
const copyMenuItem = ref<MenuInterface | null>(null)
function handleCopyMenu(menuItem: MenuInterface) {
  copyMenuItem.value = menuItem
  scissorMenuItem.value = null
}


/**
 * 处理剪切菜单选项
 *
 * @param menuItem 菜单项接口，包含菜单项的具体信息
 */
const scissorMenuItem = ref<MenuInterface | null>(null)
function handleScissorMenu(menuItem: MenuInterface) {
  copyMenuItem.value = menuItem
  scissorMenuItem.value = menuItem
}

/**
 * 处理粘贴菜单的函数
 *
 * @returns 无返回值
 */
async function handlePasteMenu(currentMenuItem: MenuTreeInterface | null) {
  /**
   * 更新菜单排序并重新获取菜单列表
   *
   * @param items 菜单项数组
   */
  const updateSortAndFetch = async (items: Array<MenuInterface>) => {
    const promiseArr = items.map(updateMenu);
    await Promise.all(promiseArr);
    const res = await getMenuList({ appId: props.appId });
    dataSource.value = res.data;
  };

  /**
   * 重置排序并展开树结构
   *
   * @param arr 菜单项数组
   * @param oTreeChildren 树形菜单项数组
   */
  const resetSortAndExpandTree = (arr: Array<MenuInterface>, oTreeChildren: Array<MenuTreeInterface>) => {
    oTreeChildren.forEach((item, index) => {
      item.sort = index;
      arr.push(item);
    });
  };

  /**
   * 递归展开树形结构
   *
   * @param pId 父节点的ID
   * @param arr 原始菜单数组
   * @param oTree 待展开的树形结构数组
   * @param pSort 父节点的排序值，默认为-1
   */
  let ms = new Date().getMilliseconds()
  const expandTree = (pId: string | undefined, arr: Array<MenuInterface>, oTree: Array<MenuTreeInterface>, pSort = -1) => {
    oTree.forEach((item) => {
      item.parentId = pId;
      item.id = generateRandomString();
      item.code = item.code + (ms + '');
      ms++;
      if (pSort > -1) item.sort = pSort;
      arr.push(item);
      if (item.children && item.children.length) {
        expandTree(item.id, arr, item.children);
      }
    });
  };

  /**
   * 处理粘贴操作
   *
   * @param parentId 父级菜单ID
   * @param sort 排序值
   */
  async function handlePaste(parentId: string | undefined, sort: number) {
    if (scissorMenuItem.value) {
      copyMenuItem.value!.parentId = parentId;
      copyMenuItem.value!.sort = sort;
      await updateMenu(copyMenuItem.value);
    } else {
      const oArr: Array<MenuInterface> = [];
      expandTree(parentId, oArr, [copyMenuItem.value as MenuTreeInterface], sort);
      const promiseArr = oArr.map(addMenu);
      await Promise.all(promiseArr);
    }
    ElMessage.success('粘贴成功');
    getList();
  };

  // 在文件夹下点击粘贴菜单项
  if (currentMenuItem) {
    // 如果当前节点存在子节点，则更新其子节点列表的排序并重新获取菜单列表
    if (currentMenuItem.children && currentMenuItem.children.length) {
      const arr: Array<MenuInterface> = [];
      resetSortAndExpandTree(arr, currentMenuItem.children);
      await updateSortAndFetch(arr);
    }
    const pId = currentMenuItem.id;
    const sort = currentMenuItem.children?.length || 0;
    await handlePaste(pId, sort);
  } else {
    // 粘贴到最外层菜单项
    const arr: Array<MenuInterface> = [];
    resetSortAndExpandTree(arr, dataSource.value as MenuTreeInterface[]);
    await updateSortAndFetch(arr);
    const pId = undefined;
    const sort = dataSource.value?.length || 0;
    await handlePaste(pId, sort);
  }

  scissorMenuItem.value = null;
  copyMenuItem.value = null;
}

/**
 * 从树形结构中删除指定的节点
 *
 * @param node 要删除的节点对象
 * @param data 节点对应的数据对象
 */
function remove(node: Node, data: MenuInterface) {
  ElMessageBox.confirm('确定删除该菜单吗？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    deleteMenu(data.id).then((res: any) => {
      ElMessage.success('删除成功')
      getList()
    })
  }).catch(() => {
  })
}

const visibleEditItem = ref(false)
const currentMenuItemId = ref<string | null>('')

/**
 * 编辑树形数据项
 *
 * @param data 树形数据项
 */
function edit(data: MenuInterface) {
  isAddOrEdit.value = 2
  visibleEditItem.value = true
  currentMenuItemId.value = data.id
  menuDetail(data.id).then((res: any) => {
    let tFormData = res.data
    if (tFormData.type == '3') {
      tFormData.isCustom = true
      tFormData.data = tFormData.data.path
      tFormData.type = '2'
    } else {
      tFormData.isCustom = false
    }
    formData.value = tFormData
  })
}

/**
 * 取消编辑项
 *
 * 将编辑项的显示状态设置为 false，并将编辑项的值重置为一个空的 MenuInterface 对象。
 */
function cancelEditItem() {
  editMenuDialogRef.value && editMenuDialogRef.value.clearValidate()
  visibleEditItem.value = false
  formData.value = {
    id: '',
    label: '',
    code: '',
    data: undefined,
    type: '',
    appId: '',
    delFlag: '',
    extra: {
      webShowFlag: 1
    },
    isCustom: false
  }
  currentMenuItemId.value = null
}

/**
 * 提交编辑后的项目
 *
 * @returns 无返回值
 */
const editMenuDialogRef = ref()

function submitEditItem() {
  editMenuDialogRef.value.validate((valid: boolean) => {
    if (valid) {
      visibleEditItem.value = false;
      // 如果是自定义页面，则type为3
      if (formData.value.isCustom) {
        formData.value.type = '3'
        formData.value.data = { path: formData.value.data }
      }
      if (isAddOrEdit.value == 1) {
        addMenu(formData.value).then((res: any) => {
          ElMessage.success('添加成功')
          getList()
        })
      } else if (isAddOrEdit.value == 2) {
        updateMenu(formData.value).then((res: any) => {
          ElMessage.success('修改成功')
          getList()
        })
      }
      formData.value = {
        id: '',
        label: '',
        code: '',
        data: undefined,
        type: '',
        appId: '',
        delFlag: '',
        extra: {
          webShowFlag: 1
        },
        isCustom: false
      }
      currentMenuItemId.value = null
    }
  })
}

/**
 * 编辑页面函数
 *
 * @param data MenuInterface - 要编辑的页面数据
 */
function editPage(data: MenuInterface) {
  emits('selectEditPage', data)
}

function getList() {
  getMenuList({ appId: props.appId }).then((res: any) => {
    dataSource.value = res.data
  })
}

/**
 * 改变自定义标志状态并更新数据
 *
 * 如果 formData.value.isCustom 为 true 且 formData.value.data 为空对象，
 * 则将 formData.value.data 设置为空字符串。
 * 否则，将 formData.value.data 设置为空对象。
 */
function changeIsCustom() {
  if (formData.value.isCustom && (JSON.stringify(formData.value.data) == '{}')) {
    formData.value.data = ''
  } else {
    formData.value.data = {}
  }
}

/**
 * 判断给定的类型是否为文件夹
 *
 * @param type 类型标识，'1'表示文件夹，其他值表示非文件夹
 * @returns 如果type为'1'，则返回true，否则返回false
 */
function isFolder(type: string) {
  return type == '1'
}

/**
 * 判断传入的类型是否为菜单类型
 *
 * @param type 类型字符串
 * @returns 如果是菜单类型则返回true，否则返回false
 */
function isMenu(type: string) {
  return type == '2' || type == '3'
}

/**
 * 判断是否为自定义页面
 *
 * @param type 页面类型
 * @returns 如果页面类型为 '3'，则返回 true，否则返回 false
 */
function isCustomPage(type: string) {
  return type == '3'
}

/**
 * 判断是否允许放置节点
 *
 * @param currentNode 当前节点
 * @param targetNode 目标节点
 * @param c 上下文对象
 * @returns 如果目标节点的类型等于 '1'，则返回 true，否则返回 false
 */
function allowDrop(currentNode: any, targetNode: any, position: any) {
  return (targetNode.data.type == '1' && position == 'inner') ||
    position == 'next' || position == 'prev';
}




/**
 * 处理节点拖拽放置的函数
 *
 * @param draggingNode 被拖拽的节点
 * @param parentDropNode 放置节点的父节点
 * @param position 放置位置
 * @param ev 拖拽事件对象
 */
async function handleNodeDrop(draggingNode: any, parentDropNode: any, position: string, ev: DragEvent) {
  // 更新sort
  setSort(dataSource.value)
  // 获取放置间隙的父节点，最外层为null
  let pId = null;
  for (let i = 0; i < dataSource.value.length; i++) {
    pId = getParentNode(draggingNode.data, dataSource.value[i])
    if (pId) {
      break;
    }
  }
  //更新parentId
  resetParentId(dataSource.value, draggingNode.data.id, pId)

  // 将menuList的数据 全部请求一次
  let updateNodeList = [] as any[]

  /**
   * 深度优先遍历给定列表，并将遍历到的节点添加到 updateNodeList 中
   *
   * @param list 待遍历的列表
   */
  function _dfs(list: any) {
    for (let i = 0; i < list.length; i++) {
      updateNodeList.push({ ...list[i] })
      if (list[i].children && list[i].children.length > 0) {
        _dfs(list[i].children)
      }
    }
  }

  _dfs(dataSource.value)

  let reqList = [] as any[]
  for (let i = 0; i < updateNodeList.length; i++) {
    reqList.push(updateMenu(updateNodeList[i]))
  }
  Promise.all(reqList).then(() => {
    getList()
    ElMessage.success('更新成功')
  })
}




/**
 * 右键菜单
 */
const showRightMenu = ref(false)
const rightMenuPos = ref([] as any)

/**
 * 打开右键菜单
 *
 * @param event 事件对象
 */
const currentRightData = ref()
const currentRightNode = ref()
const currentHightLight = ref()
function openMenu(event: any, data: any, node: any) {
  let dialogLeft = document.querySelector('.treeContent')?.getBoundingClientRect().left
  let dialogTop = document.querySelector('.treeContent')?.getBoundingClientRect().top

  let left = event.clientX - (dialogLeft ? dialogLeft : 0)
  let top = event.clientY - (dialogTop ? dialogTop : 0)
  showRightMenu.value = true
  rightMenuPos.value = [left, top]
  currentRightData.value = data
  currentRightNode.value = node
  currentHightLight.value = node.id
}

/**
 * 当节点被点击时调用的函数。
 *
 * @param data 任何类型的数据，传递给函数的数据参数。
 * @param node 任何类型的节点对象，被点击的节点对象。
 */
function nodeClick(data: any, node: any) {
  // 再次点击相同的项会取消高亮选中状态
  if (currentHightLight.value == node.id) {
    currentHightLight.value = ''
    currentRightData.value = null
  } else {
    currentHightLight.value = node.id
    currentRightData.value = data
  }
}

/**
 * 处理树形组件失去焦点事件
 *
 * @param data 任意类型的数据
 * @param node 树形组件的节点对象
 */
function handleTreeBlur(blur: boolean) {
  //如果失焦，则关闭右键菜单
  if (blur) {
    showRightMenu.value = false
  } else {
    setTimeout(() => {
      showRightMenu.value = false
    }, 300)
  }
}

const expandList = ref([] as any)

watch(() => props.dialogVisible, (val) => {
  visible.value = val
  if (val) {
    // dataSource.value = props.treeData
    getList()
  }
}, {
  immediate: true
})

watch(() => currentRightData.value, (val) => {
  if (!val) {
    showRightMenu.value = false
  }
}, {
  immediate: true,
  deep: true
})
</script>
<template>
  <!-- 菜单树 -->
  <el-dialog v-model="visible" destroy-on-close append-to-body center title="编辑菜单" width="900px" @close="cancel">
    <div class="treeContent">
      <!--  右键菜单 -->
      <right-menu v-model="showRightMenu" :position="rightMenuPos" msg="">
        <template v-slot>
          <div class="menuItemButton" id="rightMenu">
            <el-button class="rightMenuBtn" icon="Folder" v-if="isFolder(currentRightData?.type)" link
              @click.stop="append(currentRightData, '1')">
              添加文件夹
            </el-button>
            <el-button class="rightMenuBtn" v-if="isFolder(currentRightData?.type)" icon="List" link
              @click.stop="append(currentRightData, '2')">
              添加菜单
            </el-button>

            <el-button class="rightMenuBtn" link icon="Edit" @click.stop="edit(currentRightData)">
              编辑<span>{{ isFolder(currentRightData?.type) ? '文件夹' : isMenu(currentRightData?.type) ? '菜单' : '' }}</span>
            </el-button>
            <el-button class="rightMenuBtn" icon="Document"
              v-if="(!isCustomPage(currentRightData?.type)) && isMenu(currentRightData?.type)" link
              style="margin-left: 4px" @click.stop="editPage(currentRightData)">
              编辑页面
            </el-button>
            <el-button class="rightMenuBtn" link icon="Delete" @click.stop="remove(currentRightNode, currentRightData)">
              删除
            </el-button>
            <el-button class="rightMenuBtn" icon="CopyDocument" link @click="handleCopyMenu(currentRightData)">复制
            </el-button>
            <el-button class="rightMenuBtn" icon="Scissor" link
              @click="handleScissorMenu(currentRightData)">剪切</el-button>
            <el-button class="rightMenuBtn" v-if="copyMenuItem && copyMenuItem.id && currentRightData.type == '1'"
              icon="DocumentChecked" link @click="handlePasteMenu(currentRightData)">粘贴
            </el-button>
          </div>
        </template>
      </right-menu>
      <div class="treeContentTop">
        <el-button :icon="Folder" style="margin-bottom:10px;" text type="warning"
          v-show="!currentRightData || currentRightData?.type == 1" @click.stop="append(null, '1')">添加文件夹
        </el-button>
        <el-button :icon="List" style="margin-bottom:10px;" text type="primary"
          v-show="!currentRightData || currentRightData?.type == 1" @click.stop="append(null, '2')">添加菜单
        </el-button>
        <el-button icon="DocumentChecked" v-if="copyMenuItem" style="margin-bottom:10px;" text
          @click.stop="handlePasteMenu(null)">粘贴至顶级
        </el-button>
      </div>
      <el-tree :default-expanded-keys="expandList" @node-click="nodeClick" :allow-drop="allowDrop" :data="dataSource"
        draggable empty-text="请添加菜单" node-key="id" style="margin-top:10px" @node-drop="handleNodeDrop">
        <template #default="{ node, data }">
          <div v-blur="handleTreeBlur" aria-label="选中" :class="['custom-tree-node', (currentHightLight == node.id) ? 'is-current' : '',
            (scissorMenuItem && scissorMenuItem.id == data.id) ? 'scissor' : '']" role="button" tabindex="0"
            @contextmenu.prevent="openMenu($event, data, node)">
            <div style="display: flex;justify-content: flex-start;">
              <span :class="{ 'isFolder': isFolder(data.type), 'isMenu': isMenu(data.type), 'menuLabel': true }">
                <el-icon v-if="data.icon" style="margin-right: 4px;">
                  <component :is="data.icon" />
                </el-icon>
                {{ data.label }}{{ isCustomPage(data.type) ? '（自定义页面）' : '' }}
              </span>
              <span :class="{ 'isFolder': isFolder(data.type), 'isMenu': isMenu(data.type), 'engName': true }">
                {{
                  data.code == '' ? '（未配置英文名）' : '英文名：' + data.code
                }}
              </span>
            </div>
          </div>
        </template>
      </el-tree>
    </div>
    <div class="dialog-footer">
      <el-button type="primary" @click="cancel">关闭</el-button>
    </div>
  </el-dialog>
  <!-- 菜单名称和code编辑框 -->
  <el-dialog v-model="visibleEditItem" :title="`编辑${isFolder(formData.type) ? '文件夹' : '菜单'}信息`" @close="cancelEditItem"
    center width="700px">
    <div>
      <el-form ref="editMenuDialogRef" :model="formData" :rules="rules" label-width="100px" @submit.prevent>
        <el-form-item label="名称" prop="label">
          <el-input v-model="formData.label" placeholder="请输入名称"></el-input>
        </el-form-item>
        <el-form-item label="英文名" prop="code">
          <el-input v-model="formData.code" placeholder="请输入英文名"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-select v-model="formData.icon" clearable filterable placeholder="请选择菜单图标" @clear="formData.icon = ''">
            <el-option v-for="item in elPlusIcons" :value="item">
              <template #default>
                <div style="display: flex; align-items: center;">
                  <el-icon size="20" style="margin-right: 5px;">
                    <component :is="item" />
                  </el-icon>
                  <span>{{ item }}</span>
                </div>
              </template>
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item v-if="isMenu(formData.type)" label="自定义页面" prop="isCustom">
          <el-radio-group v-model="formData.isCustom" @change="changeIsCustom">
            <el-radio :value="true" label="是"></el-radio>
            <el-radio :value="false" label="否"></el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="显示此菜单">
          <el-radio-group v-model="formData.extra.webShowFlag">
            <el-radio :value="1" label="是"></el-radio>
            <el-radio :value="2" label="否"></el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item v-if="formData.isCustom === true && formData.type == '2'" label="自定义页面" prop="data">
          <el-input v-model="formData.data" placeholder="请输入自定义页面文件名（如 myInfo/index）">
            <template #prepend>
              <span>src/views/</span>
            </template>
            <template #append>.vue</template>
          </el-input>
        </el-form-item>
      </el-form>
    </div>
    <div class="dialog-footer">
      <el-button @click="cancelEditItem">关闭</el-button>
      <el-button type="primary" @click="submitEditItem">保存更改</el-button>
    </div>

  </el-dialog>

</template>
<style lang='scss' scoped>
.rightMenuBtn {
  display: flex;
  justify-content: start;
  width: 100%;
  text-align: left;
  border-bottom: 1px solid #e7e7e7 !important;
  border-radius: 0 !important;
  line-height: 30px;
  padding-left: 20px !important;
  margin-left: 0 !important;
  color: rgb(42, 42, 42) !important;
  font-size: 13px;
  vertical-align: baseline;

  &>::deep(.el-button [class*=el-icon]+span) {
    flex-grow: 1;
  }
}

.rightMenuBtn:hover {
  background: #ececec !important;
}

.is-current {
  background-color: #ecf5ff;
}

.isFolder {
  color: #e6a23c;
}

.isMenu {
  color: #409eff;

}

.dialog-footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 20px;
}

.treeContent {
  position: relative;
  width: 100%;

  &Top {
    border-bottom: 1px solid #ebeef5;
    height: 43px;
  }
}

.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding: 2px 8px 2px 2px;
}

.engName {
  min-width: 200px;
  width: fit-content;
}

.menuLabel {
  display: flex;
  align-items: center;
  min-width: 350px;
  width: fit-content;
}

.scissor {
  filter: opacity(0.4);
  padding-left: 20px;

  &::before {
    content: '✂';
    color: rgb(255, 0, 0);
    font-size: 12px;
    position: absolute;
    top: 3px;
    left: 5px;

  }
}
</style>